'''
LDI
LDF
LDC
LDS
LDSY
LDL
LDD
LDFN
DEF
APPLY
RET
CMP
JMP

'''
import compiler
import values as types


def add(vm,args,env):
    s = 0
    for i in args:
        if isinstance(i, types.Float) or isinstance(i, types.Integer):
            s+=i.value
        else:
            raise Exception()
    if type(s) == type(0):
        return types.Integer(s)
    else:
        return types.Float(s)

def sub(vm,args,env):
    s = args[0].value
    index = 1
    while index <  len(args):
        i = args[index]
        if isinstance(i, types.Float) or isinstance(i, types.Integer):
            s-=i.value
        else:
            raise Exception()
        index +=1
    if type(s) == type(0):
        return types.Integer(s)
    else:
        return types.Float(s)
def multi(vm,args,env):
    s = args[0].value
    index = 1
    while index <  len(args):
        i = args[index]
        if isinstance(i, types.Float) or isinstance(i, types.Integer):
            s*=i.value
        else:
            raise Exception()
        index +=1
    if type(s) == type(0):
        return types.Integer(s)
    else:
        return types.Float(s)
def div(vm,args,env):
    s = args[0].value
    index = 1
    while index <  len(args):
        i = args[index]
        if isinstance(i, types.Float) or isinstance(i, types.Integer):
            s/=i.value
        else:
            raise Exception()
        index +=1
    if type(s) == type(0):
        return types.Integer(s)
    else:
        return types.Float(s)
def mod(vm,args,env):
    s = args[0].value/args[1].value
    if type(s) == type(0):
        return types.Integer(s)
    else:
        return types.Float(s)
def is_symbol(vm,args,env):
    return types.Bool(isinstance(args[0],types.Symbol))
def is_integer(vm,args,env):
    return types.Bool(isinstance(args[0],types.Integer))
def is_char(vm,args,env):
    return types.Bool(isinstance(args[0],types.Char))
def is_float(vm,args,env):
    return types.Bool(isinstance(args[0],types.Float))
def is_dict(vm,args,env):
    return types.Bool(isinstance(args[0],types.Dict))
def is_list(vm,args,env):
    return types.Bool(isinstance(args[0],types.List))
def is_bool(vm,args,env):
    return types.Bool(isinstance(args[0],types.Bool))
def is_string(vm,args,env):
    return types.Bool(isinstance(args[0],types.String))
def is_nil(vm,args,env):
    return types.Bool(isinstance(args[0],types.Nil))
def is_lambda(vm,args,env):
    return types.Bool(isinstance(args[0],types.Lambda))
def is_func(vm,args,env):
    return types.Bool(isinstance(args[0],types.Func))
def is_keyword(vm,args,env):
    return types.Bool(isinstance(args[0],types.Keyword))

def gt(vm,args,env):
    return types.Bool(args[0].value > args[1].value)
def lt(vm,args,env):
    return types.Bool(args[0].value < args[1].value)
def ge(vm,args,env):
    return types.Bool(args[0].value >= args[1].value)
def le(vm,args,env):
    return types.Bool(args[0].value <= args[1].value)
def eq(vm,args,env):
    return types.Bool(args[0].value == args[1].value)

class Env:
    def __init__(self,up = None):
        self.up = up
        self.current = {}
    def get(self,key):
        obj = self
        while obj != None:
            if key in obj.current:
                return obj.current[key]
            else:
                obj = obj.up
        return None
    def set(self,key,v):
        self.current[key] = v


class Interp:
    def __init__(self):
        self.env = Env()
        self.compiler = compiler.Compiler()
        self.regist_global_func("+",add)
        self.regist_global_func("-",sub)
        self.regist_global_func("*",multi)
        self.regist_global_func("/",div)
        self.regist_global_func("%",mod)

        self.regist_global_func("symbol?",is_symbol)
        self.regist_global_func("float?",is_float)
        self.regist_global_func("integer?",is_integer)
        self.regist_global_func("list?",is_list)
        self.regist_global_func("bool?",is_bool)
        self.regist_global_func("char?",is_char)
        self.regist_global_func("keyword?",is_keyword)
        self.regist_global_func("func?",is_func)
        self.regist_global_func("lambda?",is_lambda)
        self.regist_global_func("dict?",is_dict)
        self.regist_global_func("nil?",is_nil)
        self.regist_global_func("string?",is_string)

        self.regist_global_func(">",gt)
        self.regist_global_func("<",lt)
        self.regist_global_func("=",eq)
        self.regist_global_func(">=",ge)
        self.regist_global_func("<=",le)


    def regist_global_func(self,key,func):
        self.env.set(key,types.Func(func))
    def regist_global(self,key,val):
        self.env.set(key,val)
    def apply(self,lam,args,env):
        if isinstance(lam, types.Lambda):
            e = Env(env)
            index = 0
            for i in lam.args:
                e.set(i,args[index])
                index += 1
            return self.eval(lam.value,e)
        else:
            e = Env(env)
            return lam.value(self,args,env)
    def begin(self,ast,env):
        index = 1
        while index < len(ast):
            tmp = self.eval(ast[index],env)
            index +=1
        return tmp
    def cond(self,ast,env):
        '''
        (cond [() ()]
              [() ()]
                )
        '''
        index = 1
        while index < len(ast):
            tmp = self.eval(ast[index][0],env)
            if not isinstance(tmp, types.Bool):
                raise Exception()
            if tmp.value:
                return self.eval(ast[index][1],env)
                break
            index +=1
        raise Exception()
    def run(self,s):
        r = self.compiler.compile(s)
        return self.eval(r,self.env)
    def compile(self,ast,env):
        if not isinstance(ast[1], types.String):
            raise Exception()
        return types.List(self.compiler.compile(ast[1].value))
    def quote(self,ast,env):
        if type(ast[1]) == type([]):
            return types.List(ast[1])
        else:
            return ast[1]
    def define(self,ast,env):
        if not isinstance(ast[1], types.Symbol):
            raise Exception()
        val = self.eval(ast[2],env)
        self.regist_global(ast[1].value,val)
    def eval(self,ast,env):
        if type(ast) == type([]):
            if len(ast) > 0:
                func = ast[0]
                if type(func) == type([]):
                    func = self.eval(func,env)
                else:
                    if isinstance(func, types.Symbol):
                        if func.value =='begin':
                            return self.begin(ast,env)
                        elif func.value =='cond':
                            return self.cond(ast,env)
                        elif func.value =='apply':
                            func = self.eval(ast[1],env)
                            arglist = self.eval(ast[2],env)
                            args = []
                            if not isinstance(arglist,types.List):
                                raise Exception()
                            for arg in arglist.value:
                                args.append(self.eval(arg,env))
                            return self.apply(func,args,env)
                        elif func.value =='eval':
                            arglist = self.eval(ast[1],env)
                            if not isinstance(arglist,types.List):
                                raise Exception()
                            return self.eval(arglist.value,env)
                        elif func.value =='compile':
                            return self.compile(ast,env)
                        elif func.value =='quote':
                            return self.quote(ast,env)
                        elif func.value =='lambda':
                            arglist = [x.value for x in ast[1]]
                            body = ast[2]
                            return types.Lambda(body,arglist)
                        elif func.value =='define':
                            return self.define(ast,env)
                        else:
                            s = env.get(func.value)
                            if not s:
                                raise Exception()
                            args = []
                            index = 1
                            while index < len(ast):
                                args.append(self.eval(ast[index],env))
                                index += 1
                            return self.apply(s,args,env)
                if isinstance(func, types.Lambda):
                    args = []
                    index = 0
                    while index < len(ast)-1:
                        tmp = self.eval(ast[index],env)
                        index += 1
                    return self.apply(func,args,env)
            else:
                return types.Nil()
        else:
            if isinstance(ast, types.Symbol):
                s = env.get(ast.value)
                if not s:
                    raise Exception("undefined var %s"%(ast.value,))
                else:
                    return s
            return ast


